/*
 * @Description: 平行坐标
 * @Author: YoungWind
 * @Date: 2020-09-24 10:28:11
 * @LastEditTime: 2020-09-25 13:12:26
 * @LastEditors: YoungWind
 * @FilePath: \ccd3\src\ccd3\parallel\charts\parallel.coordinates.js
 */

import BaseChart from '../../BaseChart.js'

export default class ParallelCoordinates extends BaseChart {
  constructor(id, data, tableId) {
    super(id, data)
    this.names = null;
    this.attributes = null;

    //待过滤字段
    this.filterKeys = [];
    //分类字段
    this.classificationKey = [];
    //连续字段
    this.continuousKeys = [];
    //离散字段
    this.discreteKeys = [];

    this.pathGroup = null;
    this.paths = null;
    this.yAxisGroup = null;
    this.yAxes = null;

    //视图 过渡效果
    this.transition = null;
    this.duration = 1000;

    //视图 表格
    this.tableId = tableId;
    this.table = null;
    this.thead = null;
    this.tbody = null;
  }

  setClassificationKey(name) {
    this.classificationKey = name;
    return this;
  }

  setFilterKeys(arr) {
    if (arr instanceof Array) {
      this.filterKeys = arr;//待过滤字段
    }
    else
      throw new TypeError('必须传入由名称构成的数组');
    return this;
  }

  setContinuousKeys(arr) {
    if (arr instanceof Array)
      this.continuousKeys = arr;
    else
      throw new TypeError('必须传入由名称构成的数组');
    return this;
  }

  setDiscreteKeys(arr) {
    if (arr instanceof Array)
      this.discreteKeys = arr;
    else
      throw new TypeError('必须传入由名称构成的数组');
    return this;
  }

  init() {
    this.initSvg();
    this.initMainGroup();
    this.initCoordinatesGroup();//初始化主题内容部分
    this.initTransition();//初始化过渡效果
    this.initStyle();//初始化样式
    this.initTable();//初始化表格
    return this;
  }

  render() {
    this.renderData();//数据处理
    this.renderScale();//比例尺
    this.renderMainGroup();
    this.renderBrush();//刷子
    this.renderActiveBrush();//刷子
    return this;
  }

  renderActiveBrush() {
    let _ = this;
    let numberBrushes = this.activeBrushes.size;

    if (numberBrushes == 0) {
      this.paths.classed("hidden", d => { return false });
      this.paths.classed("show", d => { return true });
    } else {
      this.paths.classed("hidden", d => {
        let return_value = false;
        this.activeBrushes.forEach((value, key) => {
          let y0 = value[0];
          let y1 = value[1];
          let value_y = _.yAxisScales.get(key)(d[key]);
          if (value_y <= y1 && value_y >= y0) {
            ;
          }
          else {
            return_value = true;
          }
        });
        return return_value;
      });
      this.paths.classed("show", d => {
        let return_value = true;
        //iterate over Map and get y-values
        this.activeBrushes.forEach((value, key) => {
          let y0 = value[0];
          let y1 = value[1];
          //check if car value is inside an active brush
          let value_y = _.yAxisScales.get(key)(d[key]);
          if (value_y <= y1 && value_y >= y0) {
            ;
          }
          else {
            return_value = false;
          }
        });
        //variable to see the active paths in this scope
        return return_value;
      });
    }
  }

  renderBrush() {
    this.activeBrushes = new Map();
    this.brushes = this.yAxes.append("g").call(d3.brushY()
      .extent([[-10, 0], [10, this.innerHeight]])
      .on("brush", this.brushed)
      .on("end", this.brushEnd)
    );
  }

  brushed = (event, attribute) => {
    this.activeBrushes.set(attribute, event.selection);
    this.renderActiveBrush();
  }

  brushEnd = (event, attribute) => {
    this.renderTable(this.svg.selectAll('path.show').data());
    if (event.selection !== null)
      return null;
    this.activeBrushes.delete(attribute);
    this.renderActiveBrush();
  }

  renderMainGroup() {
    this.renderPathGroup();
    this.renderYAxisGroup();
  }
  renderPathGroup() {
    this.paths = this.pathGroup
      .selectAll(`path`)
      .data(this.data, d => d['名称'])
      .join(
        enter => enter.append('path'),
        update => update,
        exit => exit
      )
      .attr('fill', 'none')
      .attr("stroke", d => this.color(d[this.classificationKey]))
      .attr("stroke-opacity", '0.4')
      .attr("d", d => d3.line()
        .defined(([, value]) => value != null)
        .y(([key, value]) => this.yAxisScales.get(key)(value))
        .x(([key]) => this.xAxisScale(key))
        (d3.cross(this.attributes, [d], (key, d) => [key, d[key]])));

    //添加延时效果
    this.pathGroup.selectAll(`path`)
      .transition(this.transition)
      .attrTween("stroke-dasharray", function () {
        let length = this.getTotalLength();
        return d3.interpolate(`0,${length}`, `${length},${length}`);
      })
  }

  renderYAxisGroup() {
    let _ = this;
    this.yAxes = this.yAxisGroup.selectAll(`.yAxis`)
      .data(this.attributes)
      .join(enter => enter
        .append('g')
        .attr('class', '.yAxis'),
        update => update,
        exit => exit.remove()
      )
      .call(g => g.append("text")
        .attr("y", -20)
        .attr("text-anchor", "start")
        .attr("fill", "#212121")
        .style("font-size", "1.25em")
        .text(d => d)
      )

    this.yAxes
      .transition(this.transition)
      .attr('transform', d => `translate(${this.xAxisScale(d)},0)`)//g标签根据X的比例尺位置平移
      .each(function (d) {//给每个标签添加坐标轴
        d3.select(this).call(
          d3.axisRight(_.yAxisScales.get(d))
        );
      })
  }

  renderScale() {
    this.color = d3.scaleOrdinal()
      .domain(this.names)
      .range(d3.schemeTableau10);

    this.xAxisScale = d3.scalePoint()
      .domain(this.attributes)
      .range([0, this.innerWidth]);

    this.yAxisScales = new Map();
    //添加坐标轴哈希表
    this.attributes.forEach(attribute => {
      //离散比例尺
      if (this.discreteKeys.includes(attribute)) {
        this.yAxisScales.set(
          attribute,
          //d3.extent会得到最小值和最大值
          //离散domain
          //连续range
          d3.scalePoint()
            .domain(this.data.map(item => item[attribute]))
            .range([this.innerHeight, 0])
        );
      }
      //连续比例尺
      else if (this.continuousKeys.includes(attribute)) {
        this.yAxisScales.set(
          attribute,
          d3.scaleLinear()
            .domain(d3.extent(this.data, item => +item[attribute]))
            .nice()
            .range([this.innerHeight, 0])
        );
      }
    })
  }

  renderData() {
    this.attributes = this.data.columns.filter(d => !this.filterKeys.includes(d));
    let nameSets = new Set();
    this.data.forEach(d => {
      nameSets.add(d[this.classificationKey]);
    })
    this.names = Array.from(nameSets);
  }


  renderTable(data) {
    this.tbody.selectAll('tr')
      .data(data)
      .join('tr')
      .selectAll('td')
      .data(d => Object.values(d))
      .join('td')
      .text(d => d);
  }

  initCoordinatesGroup() {
    this.pathGroup = this.mg.append('g')
      .attr('id', `${this.id}PathGroup`);

    this.xAxisGroup = this.mg.append('g')
      .attr('id', `${this.id}xAxis`)
      .attr('transform', `translate(0,${this.innerHeight})`);

    this.yAxisGroup = this.mg.append('g')
      .attr('id', `${this.id}yAxis`)
  }

  initTransition() {
    this.transition = this.svg.transition()
      .duration(this.duration)
      .ease(d3.easeLinear);
  }

  initStyle() {
    this.style = this.svg.append('style')
      .text(`
                path.hidden {
                    stroke: #000; stroke-opacity: 0.01;
                }
                .parallel-coordinates-table{
                    border-collapse: collapse;
                    width: 100%;
                    margin-bottom: 1rem;
                    color: #212529;
                }
                .parallel-coordinates-table th,
                .parallel-coordinates-table td {
                    padding: 0.75rem;
                    vertical-align: top;
                    border-top: 1px solid #dee2e6;
                }

                .parallel-coordinates-table thead th {
                    vertical-align: bottom;
                    border-bottom: 2px solid #dee2e6;
                }

                .parallel-coordinates-table tbody + tbody {
                    border-top: 2px solid #dee2e6;
                }
                .table-bordered {
                    border: 1px solid #dee2e6;
                }

                .table-bordered th,
                .table-bordered td {
                    border: 1px solid #dee2e6;
                }

                .table-bordered thead th,
                .table-bordered thead td {
                    border-bottom-width: 2px;
                }
            `);
  }

  initTable() {
    this.table = d3.select(`#${this.tableId}`)
      .append('div')
      .style('max-height', '300px')
      .style('overflow-y', 'auto')
      .append('table')
      .attr('class', 'parallel-coordinates-table table-bordered');
    this.thead = this.table.append('thead');

    this.thead.append('tr').selectAll('th')
      .data(this.data.columns)
      .join('th')
      .text(d => d);

    this.tbody = this.table.append('tbody')
      .style('text-align', 'center');
  }
}